/****************************************************************************

 Copyright (C) 2009 Jean Pierre Charalambos. All rights reserved.

 Jean Pierre Charalambos (http://dis.unal.edu.co/profesores/pierre/) is an
 Affiliate Associate Professor of the Faculty of Engineering at National
 University of Colombia (http://www.unal.edu.co/).

 This file is part of maku version 0.1.

 http://code.google.com/p/maku/
 email: jpcharalambosh@unal.edu.co

 This file may be used under the terms of the GNU General Public License 
 versions 2.0 or 3.0 as published by the Free Software Foundation and
 appearing in the COPYING file included in the packaging of this file.

 This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING THE
 WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE.

*****************************************************************************/

#include "scenet.h"
#include "decimaterdialog.h"

template <typename M>
bool SceneT<M>::openModel(const string& filename) {
  releaseProperties();
  requestProperties();

  std::cout << "Loading from file '" << filename << "'\n";
  if ( IO::read_mesh(mesh, filename, rOpt )) {
    //reset state:
    setState();
    return true;
  }
  return false;
}

template <typename M>
bool SceneT<M>::loadCube() {
  releaseProperties();
  //reset read options:
  OpenMesh::IO::Options opt;
  rOpt = opt;
  mesh = createCube();
  setState();
  return true;
}

template <typename M>
bool SceneT<M>::openTexture( const QString & fname) {
   QImage texsrc;
   if (texsrc.load( fname )) {
     return setTexture( texsrc );
   }
   return false;
}

template <typename M>
bool SceneT<M>::simplifyModel() {
  decOpt->maxVertices = (int)mesh.n_vertices();
  decOpt->maxCollapses = (int)mesh.n_edges();
  DecimaterDialog * decimaterDialog = new DecimaterDialog(decOpt);
  if ( decimaterDialog->exec() ) {
    if (!Simplifier::simplify(mesh, decOpt))
      return false;
    emit contentsChanged();
    strips.clear();
    strips.stripify();
    return true;
  }
  return true;
}

template <typename M>
bool SceneT<M>::setTexture( QImage& texsrc ) {
  if ( !rOpt.vertex_has_texcoord() )
    return false;

  //desde OpenGL v 2.0 no hace falta que las dimensiones de las texturas sean potencias de 2
  #ifdef WITH_GLEW
   if (! glewIsSupported("GL_VERSION_2_0")) {
  #else
   if (QGLFormat::openGLVersionFlags() != QGLFormat::OpenGL_Version_2_0) {
  #endif
    // adjust texture size: 2^k * 2^l
    int tex_w, w( texsrc.width()  );
    int tex_h, h( texsrc.height() );

    for (tex_w=1; tex_w <= w; tex_w <<= 1) ;
    for (tex_h=1; tex_h <= h; tex_h <<= 1) ;
    tex_w >>= 1;
    tex_h >>= 1;
    texsrc = texsrc.scaled ( tex_w, tex_h, Qt::IgnoreAspectRatio, Qt::SmoothTransformation );
  }

  QImage texture( QGLWidget::convertToGLFormat ( texsrc ) );

  glPixelStorei(GL_UNPACK_ALIGNMENT, 1);
  glPixelStorei(GL_UNPACK_SKIP_ROWS,   0);
  glPixelStorei(GL_UNPACK_SKIP_PIXELS, 0);
  glPixelStorei(GL_UNPACK_ALIGNMENT,   1);
  glPixelStorei(GL_PACK_ROW_LENGTH,    0);
  glPixelStorei(GL_PACK_SKIP_ROWS,     0);
  glPixelStorei(GL_PACK_SKIP_PIXELS,   0);
  glPixelStorei(GL_PACK_ALIGNMENT,     1);

  detachTexture();

  glGenTextures(1, &texId);
  glBindTexture(GL_TEXTURE_2D, texId);

  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_REPEAT);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR);
  glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);      

  glTexImage2D(GL_TEXTURE_2D,       // target
	       0,                   // level
	       GL_RGBA,             // internal format
	       texture.width(),     // width  (2^n) in OpenGL < 2.0
	       texture.height(),    // height (2^m) in OpenGL < 2.0
	       0,                   // border
	       GL_RGBA,             // format
	       GL_UNSIGNED_BYTE,    // type
	       texture.bits() );    // pointer to pixels

  std::cout << "Texture loaded\n";
  return true;
}

template <typename M>
void SceneT<M>::drawMesh() const {
  switch(drawMode) {
    case StripArrays:
      glEnable(GL_LIGHTING);
      glShadeModel(GL_SMOOTH);
      drawOpenMesh(drawMode);
    break;
    case ColoredStrips:
      glDisable(GL_LIGHTING);
      drawOpenMesh(drawMode);
    break;
    case Points:
      glDisable(GL_LIGHTING);
      drawOpenMesh(drawMode);
    break;
    case Wired:
      GLfloat curColor[4];
      glGetFloatv(GL_CURRENT_COLOR, curColor);
      glColor4f(1.0f, 1.0f, 1.0f, 1.0f);//alpha set to 1, i.e., full intensity
      glDisable(GL_LIGHTING);
      glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
      drawOpenMesh(drawMode);
      glColor4fv(curColor);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
    break;
    case HiddenLine: {
      DrawMode dM = Wired;
      glDisable(GL_LIGHTING);
      glShadeModel(GL_FLAT);
      glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
      glColor4f( 0.0f, 0.0f, 0.0f, 1.0f );
      glDepthRange(0.01, 1.0);
      drawOpenMesh( dM );
      glPolygonMode( GL_FRONT_AND_BACK, GL_LINE);
      glColor4f( 1.0f, 1.0f, 1.0f, 1.0f );
      glDepthRange( 0.0, 1.0 );
      drawOpenMesh( dM );
      glPolygonMode( GL_FRONT_AND_BACK, GL_FILL);
      }
    break;
    case SolidFlat:
      glEnable(GL_LIGHTING);
      glShadeModel(GL_FLAT);
      drawOpenMesh(drawMode);
    break;
    case SolidSmooth:
      glEnable(GL_LIGHTING);
      glShadeModel(GL_SMOOTH);
      drawOpenMesh(drawMode);
    break;
    case ColoredVertices:
      glDisable(GL_LIGHTING);
      glShadeModel(GL_SMOOTH);
      drawOpenMesh(drawMode);
    break;
    case SolidColoredFaces:
      glDisable(GL_LIGHTING);
      glShadeModel(GL_FLAT);
      drawOpenMesh(drawMode);
      setDefaultMaterial();
    break;
    case SmoothColoredFaces:
      glEnable(GL_LIGHTING);
      glShadeModel(GL_SMOOTH);
      drawOpenMesh(drawMode);
      setDefaultMaterial();
    break;
  }
}

template <typename M>
void SceneT<M>::drawOpenMesh( DrawMode dM ) const {
  typename Mesh::ConstFaceIter    fIt(mesh.faces_begin()),
                                  fEnd(mesh.faces_end());
  typename Mesh::ConstFaceVertexIter fvIt;

  switch(dM) {
    case StripArrays: {
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, mesh.vertex_normals());
      if ( texId && mesh.has_vertex_texcoords2D() ) {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords2D());
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texId);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texMode);
      }
      typename Stripifier::StripsIterator strip_it = strips.begin();
      typename Stripifier::StripsIterator strip_last = strips.end();
      // Draw all strips
      for (; strip_it!=strip_last; ++strip_it) {
        glDrawElements(GL_TRIANGLE_STRIP, strip_it->size(), GL_UNSIGNED_INT, &(*strip_it)[0] );
      }
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);
    }
    break;
    case ColoredStrips: {
      typename Stripifier::StripsIterator strip_it = strips.begin();
      typename Stripifier::StripsIterator strip_last = strips.end();
      float cmax  = 256.0f;
      int   range = 220;
      int   base  = (int)cmax-range;
      int   drcol  = 13;
      int   dgcol  = 31;
      int   dbcol  = 17;
      int rcol=0, gcol=dgcol, bcol=dbcol+dbcol;
      // Draw all strips
      for (; strip_it!=strip_last; ++strip_it) {
        typename Stripifier::IndexIterator idx_it   = strip_it->begin();
        typename Stripifier::IndexIterator idx_last = strip_it->end();
        rcol = (rcol+drcol) % range;
        gcol = (gcol+dgcol) % range;
        bcol = (bcol+dbcol) % range;
        glBegin(GL_TRIANGLE_STRIP);
        glColor3f((rcol+base)/cmax, (gcol+base)/cmax, (bcol+base)/cmax);
        for ( ;idx_it != idx_last; ++idx_it )
          glVertex3fv(&mesh.point( OM_TYPENAME Mesh::VertexHandle(*idx_it))[0]);
        glEnd();
      }
      glColor3f(1.0, 1.0, 1.0);
    }
    break;
    case Points:
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      if (mesh.has_vertex_colors() ) {
        glEnableClientState(GL_COLOR_ARRAY);
        glColorPointer(3, GL_UNSIGNED_BYTE, 0, mesh.vertex_colors());
      }
      else
        glColor3f(pointColor.redF(), pointColor.greenF(), pointColor.blueF());
      glDrawArrays( GL_POINTS, 0, mesh.n_vertices() );
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_COLOR_ARRAY);
    break;
    case Wired:
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        fvIt = mesh.cfv_iter(fIt.handle()); 
        glVertex3fv( &mesh.point(fvIt)[0] );
        ++fvIt;
        glVertex3fv( &mesh.point(fvIt)[0] );
        ++fvIt;
        glVertex3fv( &mesh.point(fvIt)[0] );
      }
      glEnd();
    break;
    case HiddenLine:
    break;
    case SolidFlat:
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        glNormal3fv( &mesh.normal(fIt)[0] );
        fvIt = mesh.cfv_iter(fIt.handle());
        glVertex3fv( &mesh.point(fvIt)[0] );
        ++fvIt;
        glVertex3fv( &mesh.point(fvIt)[0] );
        ++fvIt;
        glVertex3fv( &mesh.point(fvIt)[0] );
      }
    glEnd();
    break;
    case SolidSmooth:
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, mesh.vertex_normals());
      if ( texId && mesh.has_vertex_texcoords2D() ) {
        glEnableClientState(GL_TEXTURE_COORD_ARRAY);
        glTexCoordPointer(2, GL_FLOAT, 0, mesh.texcoords2D());
        glEnable(GL_TEXTURE_2D);
        glBindTexture(GL_TEXTURE_2D, texId);
        glTexEnvf(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, texMode);
      }
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        fvIt = mesh.cfv_iter(fIt.handle()); 
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
      }
      glEnd();
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_TEXTURE_COORD_ARRAY);

      if ( texId && mesh.has_vertex_texcoords2D() ) {
        glDisable(GL_TEXTURE_2D);
      }
    break;
    case ColoredVertices:
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, mesh.vertex_normals());
      if ( mesh.has_vertex_colors() ) {
        glEnableClientState( GL_COLOR_ARRAY );
        glColorPointer(3, GL_UNSIGNED_BYTE, 0,mesh.vertex_colors());
      }
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        fvIt = mesh.cfv_iter(fIt.handle());
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
      }
      glEnd();
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
      glDisableClientState(GL_COLOR_ARRAY);
    break;
    case SolidColoredFaces:
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, mesh.vertex_normals());
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        glColor( fIt.handle() );
        fvIt = mesh.cfv_iter(fIt.handle()); 
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
      }
      glEnd();
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
    break;
    case SmoothColoredFaces:
      glEnableClientState(GL_VERTEX_ARRAY);
      glVertexPointer(3, GL_FLOAT, 0, mesh.points());
      glEnableClientState(GL_NORMAL_ARRAY);
      glNormalPointer(GL_FLOAT, 0, mesh.vertex_normals());
      glBegin(GL_TRIANGLES);
      for (; fIt!=fEnd; ++fIt) {
        glMaterial( fIt.handle() );
        fvIt = mesh.cfv_iter(fIt.handle());
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
        ++fvIt;
        glArrayElement(fvIt.handle().idx());
      }
      glEnd();
      glDisableClientState(GL_VERTEX_ARRAY);
      glDisableClientState(GL_NORMAL_ARRAY);
    break;
  }
}
